/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

/*!
  \file vcgui3.cxx
  \author Cory Dixon
*/

#include <stdlib.h>

#include "vcgui3.h"
#include "plot_tree.h"
#include "vcConfig.h"
#include "myStripChart.h"
#include "color.h"

#include "nodeIconManager.h"
#include "fpManager.h"
#include "fbManager.h"
#include "networkLayer.h"
#include "windManager.h"
#include "targetLayer.h"

/*<-------Defines--------->*/
/*<------End Defines------>*/

/*<---Global Variables---->*/
/*! \brief determines level of output to user. */
extern bool         verbose;
extern GuiStruct    guiStr;

       int          init_var;
       FifoMsg_t  * fifo;
/*<-End Global Variables-->*/

/************************************************************\
|                  DataToXML_Parser::init                    |
\************************************************************/
/*!
  \brief the initializing function for the XML generating thread.
  \param shm shared memory struct between the gateway threads.
  \pre instance of DataToXML_Parser has been created
  \post the XML utils have been intialized and the inetTxFifo
  and SAVRxFifo have been opened
  \return TRUE on successful completion of initialization, FALSE otherwise
*/
VCGUI::VCGUI(String name, int argc, char * argv[]) : ThreadedPipe(name)
{
	cout << GREEN << "---------->>>>>      " YELLOW "VCGUI" GREEN "      <<<<<----------" << ATTROFF << endl;

	//vcgui = this;
	init_var = 0;
	isFullScreen = false;
}

bool VCGUI::init(ShmStruct * shmStructPtr)
{
	ThreadedPipe::init(shmStructPtr);
	fifo           = &fifo_msg;
	guiStr.vcgui   = this;
	guiStr.shm_ptr = shmStructPtr;

	cout << "Configuring VCGUI interface : ..................";

	// initialize a gps position for me
	Client * a_client;
	a_client = shmPtr->clients.getClient(0);
	
	//cout << "VCGUI::init - mutex_lock" << endl;
	mutex_lock();
	a_client->telemetry.gps.pos.lat = 40.1481809;
	a_client->telemetry.gps.pos.lon = -105.240005;
	a_client->telemetry.gps.pos.alt = 1670;
	a_client->telemetry.gps.track   = 0.0;
	a_client->telemetry.gps.sog     = 0.0;
	mutex_unlock();
	//cout << "VCGUI::init - mutex_unlock" << endl;
	
	// CORY FIXME -- only for now do the gps state
	a_client->state.gps = 0;

	guiStr.builder = gtk_builder_new ();
	if( gtk_builder_add_from_file (guiStr.builder, guiStr.config.glade_file.c_str(), NULL) == 0 ) {
		cout << "VCGUI -- guilder could not parse XML file: "<< guiStr.config.glade_file << endl;
		return false;
	}

	// grab my widgets of interest from the builder
	main_window    = GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "mapWindow"));
	datebar        = GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "datebar"));
	win_vbox       = GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "win_vbox"));
	map_frame      = GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "map_frame"));
	mouse_location = GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "mouse_location"));
	node_list_frame= GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "node_list"));
	layer_list_frame = GTK_WIDGET (gtk_builder_get_object(guiStr.builder, "layer_list"));

	if( !( main_window && datebar && win_vbox && map_frame && mouse_location && node_list_frame && layer_list_frame) ){
		cout << "VCGUI -- invalid XML file, does not have required wigets defined" << endl;
		return false;
	}

	// setup window level signals
	//gtk_builder_connect_signals (guiStr.builder, this);
	gtk_signal_connect(GTK_OBJECT(main_window), "key_release_event", GTK_SIGNAL_FUNC(VCGUI::vc_key_handler), this);
	gtk_signal_connect(GTK_OBJECT(main_window), "delete_event", GTK_SIGNAL_FUNC(VCGUI::vc_window_destroy), this);

	// setup the window properties
	gtk_window_set_position(GTK_WINDOW(main_window), GTK_WIN_POS_NONE);
	gtk_window_set_resizable(GTK_WINDOW(main_window), TRUE);
	gtk_window_set_default_size(GTK_WINDOW (main_window), guiStr.config.width, guiStr.config.height);
	gtk_window_set_title(GTK_WINDOW(main_window), "NetUAS-C3");

	guiStr.main_window = main_window;

	// init nodes and geo map
	node_list.init();
	createMap();

	// init toolbar
	toolbar = map.getToolBar();
	gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
	gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar),GTK_ICON_SIZE_SMALL_TOOLBAR);
	gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbar), TRUE);
	gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2);

	// add exit
	GtkToolItem *sep_tb;
	GtkToolItem *exit_tb;
	sep_tb = gtk_separator_tool_item_new();
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep_tb, 0); 

	exit_tb = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
	gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(exit_tb), "Quit VCGUI");
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), exit_tb, 0);
	g_signal_connect(G_OBJECT(exit_tb), "clicked", G_CALLBACK(VCGUI::vc_window_exit), this);

	search_entry = gtk_entry_new ();
	g_signal_connect(G_OBJECT(search_entry), "key-press-event", 
			G_CALLBACK(VCGUI::search_key_press), this);

	// insert search entry
	GtkToolItem *search_tb = gtk_tool_item_new();
	gtk_container_add(GTK_CONTAINER(search_tb), search_entry);
	gtk_tool_item_set_tooltip_text(search_tb, "Search GEO Coded position");
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), search_tb, -1);

	// show it
	gtk_widget_show (search_entry);

	// add toolbar to toolbar_frame, and make sure it is at the top
	gtk_box_pack_start(GTK_BOX(win_vbox), toolbar, 0, 0, 0);
	gtk_box_reorder_child(GTK_BOX(win_vbox), toolbar, 0);

	// show everything in the main window
	gtk_widget_show_all(main_window);

	// add update timer at 1 hz
	g_timeout_add (1000, VCGUI::updateTime, this);

	cout << " [" << BLUE << "done" << ATTROFF << "]" << endl;

	// turn on the OSM map layer to the default map source
	map.toggleLayerShown(GPSMap::OSM_LAYER, 1);

	// add myself to the client-list
	node_list.addNodes();
	((NodeIconManager*)map.layer_manager[GPSMap::ICON_LAYER])->addNodes();

	// set default view for the map to be the US at zoom level 4
	//   Pac-Man (elstonj)
	//map.zoomInLevel(7);
	//map.moveToGeo(2.25, 2.5);
	
	map.zoomInLevel(4);
	map.moveToGeo(-96.0, 39.8136);

	return true;
}

void VCGUI::exit()
{	
	ThreadedPipe::exit();
}

void VCGUI::createMap() 
{
	map.Init(map_frame);
	map.set_mouse_info_location((GtkStatusbar*)mouse_location);

	guiStr.config.gpsmap = &map;

	int n = g_list_length(guiStr.config.map_files);
	char *file;
	for( int i=0; i < n; i++ ) {
		file = (char *)g_list_nth_data( guiStr.config.map_files, i);
		cout << endl << "Adding geoImage to map: " << file << endl;
		map.add_geoImage(file);
	}
}

void VCGUI::update()
{
	int maxFD, val, n=0;
	fd_set readFDs, writeFDs;
	struct timeval timeout;

	/*<------Select FDs------->*/
	FD_ZERO(&readFDs);
	FD_ZERO(&writeFDs);

	FD_SET(fifo_FDs[INBOARD_RX],&readFDs);
	FD_SET(fifo_FDs[INBOARD_TX],&writeFDs);
	maxFD = fifo_FDs[INBOARD_RX];
	maxFD = (fifo_FDs[INBOARD_TX] > maxFD ? fifo_FDs[INBOARD_TX] : maxFD);

	timeout.tv_sec = TIME_OUT_SEC; timeout.tv_usec = TIME_OUT_USEC;

	val = select(maxFD + 1, &readFDs, &writeFDs, NULL, &timeout);

	if( val < 0 ) {
		if (errno != EINTR)
			cout << getName() << "::update() - select, " <<  strerror(errno) << endl;
	}
	else if(val == 0) {
		;
	}
	/*<----One FD is Active--->*/
	else {
		/*<---RX Operations--->*/
		if(FD_ISSET(fifo_FDs[INBOARD_RX], &readFDs)) {
			if((n = readFifo(INBOARD_RX)) > 0) {

				uint16_t length = CMD_SIZE(fifo_msg.data);
				if( length != fifo_msg.size) {
					cout << getName() << "::update() - error, length of command != length of buffer" << endl
						<< "\t CMD_SIZE=" << (int)length << " fifo_msg.size=" << (int)fifo_msg.size << endl;

					if( length < 1000 && length <= fifo_msg.size ){
						if( C_calcCheckSum((uint8_t *)buf) == 0 ) 
							cout << "\tCMD: is valid" << endl;
						else
							cout << "\tCMD: is NOT valid" << endl;
					}

					return;
				}

				Command cmd((uint8_t *)fifo_msg.data);
				uint8_t * dataPtr  = CMD_DATAPTR(fifo_msg.data);
				Client * a_client  = shmPtr->clients.getClient(fifo_msg.src);
				Client * to_client = shmPtr->clients.getClient(fifo_msg.dest);
				Client * me        = shmPtr->clients.getClient(0);

				// make sure message is for me
				if( !a_client ) {
					cout << "VCGUI::update -- could not find client from source address" <<endl;
					return;
				}
				if( to_client != me ) {
					if(verbose)
						cout << " -- NOT TO ME" << endl;
					//return;  //FIXME - this should actually be here
				}

				//cout << "VCGUI::PARSING - mutex_lock" << endl;
				mutex_lock();
				if( !cmd.isValid() )
					cout << getName() << "::update() - error, invalid command" << endl << cmd ;
				else {
					//cout << "VCGUI::PARSING -- COMMAND [" << (int)cmd.cmd() << "] " << a_client->name << endl;

#ifdef USE_GDK_THREAD
					// GTK/GDK thread enter
					gdk_threads_enter();
#endif
					//--- update node list and node's window ---//
					node_list.setCMD_RX(a_client->name, cmd.cmd());
					node_windows[ shmPtr->clients.getRefNum(a_client) ].setCMD_RX(cmd.cmd());

					switch(cmd.cmd() ) {
						case CMD_CAPABILITY: {
							//cout << "CMD_CAPABILITY" << endl;
							node_list.addNodes();
							((NodeIconManager*)map.layer_manager[GPSMap::ICON_LAYER])->addNodes();
						} break;

/*
						case CMD_TELEM:
							//cout << "CMD_TELEM" << endl;
							CopyBufToStruct( dataPtr, a_client->telemetry);
							//-- let system know we have gps data for the client --//
							a_client->state.gps = 1;
							break;

						case CMD_GPS_TELEM: {
							//cout << "CMD_GPS_TELEM" << endl;
							GPSTelem_t gps;
							CopyBufToStruct( dataPtr, gps);
							a_client->telemetry.gps.pos.lat = gps.pos.lat;
							a_client->telemetry.gps.pos.lon = gps.pos.lon;
							a_client->telemetry.gps.pos.alt = gps.pos.alt;
							a_client->telemetry.gps.track = gps.track;
							a_client->telemetry.gps.sog = gps.sog;
							//-- let system know we have gps data for the client --//
							a_client->state.gps = 1;
						} break;

						case CMD_GPS_LATLON: {
							//cout << "CMD_GPS_LATLON" << endl;
							GPSLatLon_t location;
							CopyBufToStruct( dataPtr, location);
							a_client->telemetry.gps.pos.lat = location.lat;
							a_client->telemetry.gps.pos.lon = location.lon;
							//-- let system know we have gps data for the client --//
							a_client->state.gps = 1;
						} break;
*/
						case CMD_FP_SUBS:{
							//int nPoints = (cmd.num()/sizeof(Waypt_t));
							//cout << getName() << ":: CMD_FP_SUBS received " << nPoints 
							//<< " waypoints from " << a_client->name << endl;

							int i;
							for( i=0; i < MAX_CLIENTS; i++) {
								Client *b_client = shmPtr->clients.getClient(i);
								if( b_client && b_client->name == a_client->name )
									break;
							}

							if( i < MAX_CLIENTS ) {
								FPManager *fpM = (FPManager *)map.layer_manager[GPSMap::FP_LAYER];
								bool valid_plan = false;
								if( fpM )
									valid_plan = fpM->read_from_cmd( i, (uint8_t *)fifo_msg.data);
								if( !valid_plan ) printf("Did not process waypoint plan\n");
								//else //map->show_WP_plans = true;
							} else
								cout << "Could not load waypoint plan: out of space" << endl;

						} break;

						case CMD_FP: {
							int nPoints = (cmd.num()/sizeof(Waypt_t));
							cout << getName() << ":: CMD_FP received " << nPoints 
							<< " waypoints from " << a_client->name << endl;

							FPManager *fpM = (FPManager *)map.layer_manager[GPSMap::FP_LAYER];
							bool valid_plan = false;
							if( fpM )
								valid_plan = fpM->read_from_cmd( 0, (uint8_t *)fifo_msg.data);
							if( !valid_plan ) { 
								printf("Did not process waypoint plan\n");
								NACK(INBOARD_TX);
							} else {
								// reply back with download of the flight plan
								CMD_CMD(fifo_msg.data)  = CMD_FP_SUBS;
								CMD_CSUM(fifo_msg.data) = 0;
								CMD_CSUM(fifo_msg.data) = C_calcCheckSum((uint8_t*)fifo_msg.data);

								fifo_msg.size = CMD_SIZE(fifo_msg.data);
								fifo_msg.dest = fifo_msg.src;
								fifo_msg.src.s_addr = shmPtr->clients.getAddr(me).s_addr;
								writeFifo(INBOARD_TX);

								//ACK(INBOARD_TX);
							}
						} break;

						case CMD_WIND: {
							int i;
							for( i=0; i < MAX_CLIENTS; i++) {
								Client *b_client = shmPtr->clients.getClient(i);
								if( b_client && b_client->name == a_client->name )
									break;
							}
							WindManager *wM = (WindManager *)map.layer_manager[GPSMap::WIND_LAYER];
							if( wM && i < MAX_CLIENTS)
								wM->received_data( i, (uint8_t *)fifo_msg.data);
						} break;

						case CMD_TARGET_STATUS: {
							int i;
							for( i=0; i < MAX_CLIENTS; i++) {
								Client *b_client = shmPtr->clients.getClient(i);
								if( b_client && b_client->name == a_client->name )
									break;
							}
							TargetManager *tM = (TargetManager *)map.layer_manager[GPSMap::TARGET_LAYER];
							if( tM && i < MAX_CLIENTS)
								tM->received_data( i, (uint8_t *)fifo_msg.data);
						} break;
		 
/*
						case CMD_LINK_STATUS: {
							//cout << "CMD_LINK_STATUS" << endl;
							// since the rssi trace client already sets this, no need to rewrite value
							// of client. note this is probably bad since the vcgui should subscribe, but
							// the gatewaydaemon is the vcgui.
							if( a_client != me ) {
								int num_rssi_val = cmd.num()/sizeof(LinkStatus_t);
								//cout << getName() << "-- CMD_LINK_STATUS --" << endl
								//<< "\tMessage From: " << a_client->name << " Num Links=" << num_rssi_val << endl;

								LinkStatus_t * link	= (LinkStatus_t *)dataPtr;
								for( int ii=0; ii < num_rssi_val; ii++) {
									a_client->linkstatus[ii] = link[ii];

									//printf("\t%i: IP=%i.%i.%i.%i MAC=%02X:%02X:%02X:%02X:%02X:%02X last_rx=%f\n",
									//ii, link[ii]->ip[0], link[ii]->ip[1], link[ii]->ip[2], link[ii]->ip[3],
									//link[ii]->mac[0],link[ii]->mac[1],link[ii]->mac[2],link[ii]->mac[3],link[ii]->mac[4],link[ii]->mac[5],
									//link[ii]->last_rx);
								}
							}
						} break;

						case CMD_SONDE: {
							Sonde_t sonde;
							CopyBufToStruct( dataPtr, sonde);
							//cout << "CMD_SONDE: from=" << a_client->name << " sonde=" << (int)sonde.id 
								   //<< " press=" << sonde.pth.pressure << " temp=" << sonde.pth.temp << endl;
							if( sonde.id < MAX_NUM_SONDE ) {
								a_client->sonde[ sonde.id ] = sonde;
							} else
								cout << "CMD_SONDE: invalid id=" << (int)sonde.id << endl;
	
						} break;
*/
						case CMD_ACK: {
							int cmd_id = dataPtr[0];
							//cout << "CMD_ACK: from=" << a_client->name << " cmd=" << cmd_id << endl;
							//--- update node list and node's windows ---//
							node_list.setCMD_RX(a_client->name, cmd_id, 1);
							node_windows[ shmPtr->clients.getRefNum(a_client) ].setCMD_RX(cmd_id, 1);
						} break;

						case CMD_FAIL: {
							int cmd_id = dataPtr[0];
							//cout << "CMD_FAIL: from=" << a_client->name << " cmd=" << cmd_id << endl;
							//--- update node list and node's window ---//
							node_list.setCMD_RX(a_client->name, cmd_id, 0);
							node_windows[ shmPtr->clients.getRefNum(a_client) ].setCMD_RX(cmd_id, 0);
						} break;

						default:
						  if (verbose)
								cout << getName() << "!! -- did not handle CMD=0x" << hex << (int)cmd.cmd() << dec << endl;
					}

#ifdef USE_GDK_THREAD
					// GTK/GDK thread leave
					gdk_flush();
					gdk_threads_leave();
#endif

				}
				mutex_unlock();
				//cout << "VCGUI::PARSING - mutex_unlock" << endl;
			}
		}
	}
}

VCGUI::~VCGUI()
{
	if( guiStr.builder )
		g_object_unref (G_OBJECT (guiStr.builder));
	guiStr.builder = NULL;
}

GtkWidget *VCGUI::create_quit_dialog (void)
{
	GtkDialog * quit_dialog;

	GtkWidget * main_window = GTK_WIDGET (gtk_builder_get_object (guiStr.builder, "mapWindow"));
	quit_dialog = (GtkDialog *)gtk_dialog_new_with_buttons("Quit vcGUI?",(GtkWindow *)main_window,
			GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_STOCK_OK,
			GTK_RESPONSE_ACCEPT,
			GTK_STOCK_CANCEL,
			GTK_RESPONSE_REJECT,
			NULL);

	GtkWidget *title;
	title = gtk_label_new ("Are you sure?");
	gtk_widget_show (title);
	gtk_container_add ((GtkContainer *)(quit_dialog->vbox), title);
	gtk_widget_show ((GtkWidget *)quit_dialog);
	gdk_beep();
	return (GtkWidget *)quit_dialog;
}

gboolean VCGUI::updateTime(gpointer user_data) 
{
	VCGUI *me = (VCGUI *)user_data;
	if( !me )
		return false;

	time_t rawtime;
	struct tm * timeinfo;
	char date [64];

#ifdef USE_GDK_THREAD
	// GTK/GDK thread enter
	gdk_threads_enter();
#endif

	time ( &rawtime );
	timeinfo = localtime ( &rawtime );
	strcpy(date,asctime(timeinfo));
	date[strlen(date)-1] = 0;

	gtk_statusbar_push((GtkStatusbar *)me->datebar,0,date);

#ifdef USE_GDK_THREAD
	// GTK/GDK thread leave
	gdk_flush();
	gdk_threads_leave();
#endif

	return true;
}

// If you return FALSE in the "delete_event" signal handler, GTK will emit
// the "destroy" signal. Returning TRUE means you don't want the window to be
// destroyed.  This is useful for popping up 'are you sure you want to quit?'
// type dialogs.
gboolean VCGUI::vc_window_destroy(GtkWidget * widget, GdkEventKey * event, gpointer user_data)
{
	//cout << "VCGUI::vc_window_destroy - user_data=" << user_data << endl;
	VCGUI *me = (VCGUI *)user_data;
	if( me == NULL )
		return true;

	gint response;

	GtkWidget *quit = me->create_quit_dialog();
	response = gtk_dialog_run (GTK_DIALOG (quit));

	if (response == GTK_RESPONSE_ACCEPT) {
		gtk_widget_destroy (quit);
		gtk_main_quit ();

		return false;
	}
	gtk_widget_destroy (quit);
	return true;

}

gboolean VCGUI::vc_key_handler(GtkWidget * widget, GdkEventKey * event, gpointer user_data)
{
	VCGUI *me = (VCGUI *)user_data;
	if( me == NULL ) return false;
	gint k = event->keyval;

	if( GTK_WIDGET_HAS_FOCUS(me->search_entry) )
		return false;

	//cout << "VCGUI::vc_key_handler - keyval=" << hex << k << dec 
		//<< " str=" << event->string << " user_data=" << user_data << endl;

	switch( k ) {
		case 'q': case 'Q':
			me->vc_window_destroy(widget, event, user_data);
			break;
		case 'p': case 'P':
			(void)create_param_window();
			break;
		default:
			return false; // propagate event
			break;
	}

	return true;
}

void VCGUI::vc_window_exit(GtkWidget * widget, gpointer user_data)
{
	//cout << "VCGUI::vc_window_exit - user_data=" << user_data << endl;
	VCGUI *me = (VCGUI *)user_data;
	if( me == NULL )
		return;
	me->vc_window_destroy(widget, NULL, user_data);
}

GtkBuilder * VCGUI::getBuilder() 
{ 
	return guiStr.builder; 
}

gboolean VCGUI::search_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
	gint k;
	k = event->keyval;

	if( !user_data ) return FALSE;
	VCGUI *me = (VCGUI *)user_data;

	//cout << "VCGUI::search_key_press - key=" << hex << k << dec << endl;

	// catch the enter key to start search
	if( k == GDK_Return ) {

		// get the search string
		const char *search = gtk_entry_get_text(GTK_ENTRY(widget));
		if( strlen(search) > 1 ) {
			me->map.geocoder.search(search); 
		}
		return TRUE;
	}

	// Let the number pass through
	return FALSE;
}
